package Agent;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import eis.exceptions.ActException;
import eis.iilang.Action;

import jade.core.AID;
import jade.core.Agent;

public class AgentBase extends Agent{

	protected static DocumentBuilderFactory documentbuilderfactory = DocumentBuilderFactory.newInstance();
	protected static TransformerFactory transformerfactory = TransformerFactory.newInstance();
	
	private InputStream inputstream = null;
	private OutputStream outputstream = null;

	private Socket socket;
	private String name;
	private String host;
	private int port;
	private String username;
	private String password;
	private String team;
	private String role;
	
	protected boolean connected;
	protected boolean verbose;
	private AID aids[]; 
	private int currentActionId; 
	
	protected HashMap<String,Percepts> percepts;
	protected HashSet<String> goals;
	protected HashMap<String,String> beliefs;
	
	public AgentBase()
	{
		connected = false;
		verbose = false;
		percepts = new HashMap<String,Percepts>();
		goals = new HashSet<String>();
		beliefs = new HashMap<String,String>();
		
		addBehaviour(new InitBehaviour());
		
	}

	public int getCurrentId()
	{
		return currentActionId;
	}
	
	public void setCurrentId(int id)
	{
		currentActionId = id;
	}
	
	public void init()
	{
		    
		Document doc = null;
		      try
		      {
		         doc = documentbuilderfactory.newDocumentBuilder().parse(ConfigTools.conf);
		      }
		      catch(Exception e){
		    	  doDelete();
		    	  return;
		      }
		      
		      NodeList list = doc.getElementsByTagName("agent");
		      aids = new AID[list.getLength()-1];
		      int index = 0;
		      for(int i = 0; i <list.getLength();i++)
		      {
		    	  Element e = (Element)(list.item(i));
		    	  
		    	  if(e.getAttribute("name").equals(getLocalName()))
		    	  {
		    		  name = e.getAttribute("name");
		    		  team = e.getAttribute("team");
		    		  username = e.getAttribute("username");
		    		  password = e.getAttribute("password");
		    		  host=e.getAttribute("host");
		    		  port = Integer.parseInt(e.getAttribute("port"));
		    		  verbose = Boolean.parseBoolean(e.getAttribute("verbose"));
		    		  role = e.getAttribute("role");
		    		  
		    		  if(verbose)
		    			  System.out.println("Name : \n" +team+ " "+username+" "+password+" "+host+" "+port+" "+verbose+" "+role);
		    	  }
		    	  else
		    	  {
		    		  aids[index] = new AID(e.getAttribute("name"),AID.ISLOCALNAME);
		    		  index++;
		    	  }
		    		  
		      }
		     
		      addBehaviour(new ConnexionBehaviour());
	}
	
	public AID[] getTeamAID()
	{
		return aids;
	}
	
	public boolean isConnected()
	{
		return connected;
	}
	
	public boolean isVerbose()
	{
		return verbose;
	}
	
	public String getRole()
	{
		return role;
	}
	
	public void clearAllPercepts()
	{
		percepts.clear();
	}
	
	public void addPercept(String name,String value)
	{
		if(percepts.containsKey(name))
		{
			percepts.get(name).addValue(value);
		}
		else
		{
			Percepts p =new Percepts(name);
			p.addValue(value);
			percepts.put(name, p);
		}
	}
	
	public void addPercept(Percepts p)
	{
		if(!percepts.containsKey(p.getName()))
			percepts.put(p.getName(), p);
		else
		{
			Percepts old = percepts.get(p.getName());
			for(String s : p.getValues())
			{
				old.addValue(s);
			}
		}
	}
	
	public void removePercepts(String name)
	{
		if(percepts.containsKey(name))
			percepts.remove(name);
	}
	
	public Percepts getPercepts(String name)
	{
		if(percepts.containsKey(name))
			return percepts.get(name);
		else 
			return null;
	}
	
	public Collection<Percepts> getAllPercept()
	{
		return percepts.values();
	}
	
	public void clearBeliefs()
	{
		beliefs.clear();
	}
	
	public void addBelief(String belief,String value)
	{
		beliefs.put(belief,value);
	}
	
	public void removeBelief(String belief)
	{
		if(beliefs.containsKey(belief))
			beliefs.remove(belief);
	}

	public boolean beliefExists(String belief)
	{
		return beliefs.containsKey(belief);
	}
	
	public void clearGoals()
	{
		goals.clear();
	}
	
	public void addGoal(String goal)
	{
		if(!goals.contains(goal))
			goals.add(goal);
	}
	
	public void removeGoal(String goal)
	{
		if(goals.contains(goal))
			goals.remove(goal);
	}
	
	public boolean goalExists(String goal)
	{
		return goals.contains(goal);
	}
	
	public HashSet<String> getGoals()
	{
		return goals;
	}
	
	public void Connection()
	{
			try {
				socket = new Socket(host,port);
				inputstream = socket.getInputStream();
				outputstream = socket.getOutputStream();
			} catch (UnknownHostException e) {
				System.out.println("Hote Inconnu " + e.getMessage());
				return;
			} catch (IOException e) {
				System.out.println("Erreur Socket : "+e.getMessage());
				return;
			}
			System.out.println("Socket ...OK");

			System.out.println("Authentification ....");
			boolean result = auth();
			if ( result == true ) {
				System.out.println("Réussie");
			}
			else {
				System.out.println("Echec");
				return;
			}
			
			currentActionId = -1;
			connected = true;
	}

	public boolean auth()
	{
			Document doc = null;
			
			try {
				
				doc = documentbuilderfactory.newDocumentBuilder().newDocument();
				Element root = doc.createElement("message");
				root.setAttribute("type","auth-request");
				doc.appendChild(root);
				
				Element auth = doc.createElement("authentication");
				auth.setAttribute("username",username);
				auth.setAttribute("password",password);
				root.appendChild(auth);
				
			} catch (ParserConfigurationException e) {
	
				System.err.println("Problème Création Message Authentification");
				return false;
				
			}
	
			try {
				sendDoc(doc);
			} catch (IOException e1) {
				System.out.println("Echec Envoie Message");
				System.out.println(e1.getMessage());
				return false;
			}
			
			Document reply;
			try {
				reply = receiveDoc();
			} 
			 catch (IOException e) {
	
				e.printStackTrace();
				return false;
				
			}
			
			Element root = reply.getDocumentElement();
			if (root==null) return false;
			if (!root.getAttribute("type").equalsIgnoreCase("auth-response")) return false;
			NodeList nl = root.getChildNodes();
			Element authresult = null;
			for (int i=0;i<nl.getLength();i++) {
				Node n = nl.item(i);
				if (n.getNodeType()==Element.ELEMENT_NODE && n.getNodeName().equalsIgnoreCase("authentication")) {
					authresult = (Element) n;
					break;
				}
			}
			if (!authresult.getAttribute("result").equalsIgnoreCase("ok")) return false;
	
			return true;
			
		}
	
	public Document receiveDoc() throws IOException
	{

			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			int read = inputstream.read();
			while (read!=0) {
				if (read==-1) {
					throw new IOException(); 
				}
				buffer.write(read);
				try {
					read = inputstream.read();
				} catch (IOException e) {
					throw new IOException("Reading from input-stream failed.");
				}
			}
		
			byte[] raw = buffer.toByteArray();
			
			Document doc;
			try {
				doc = documentbuilderfactory.newDocumentBuilder().parse(new ByteArrayInputStream(raw));
			} catch (Exception e) 
			{
				System.out.println("Erreur Parsing Message Reçu");
				return null;
			}
			
			return doc;
	}

	public void sendDoc(Document doc) throws IOException
	{
		
			try {
				transformerfactory.newTransformer().transform(new DOMSource(doc),new StreamResult(outputstream));
				ByteArrayOutputStream temp = new ByteArrayOutputStream();
				transformerfactory.newTransformer().transform(new DOMSource(doc),new StreamResult(temp));
				outputstream.write(0);
				outputstream.flush();

			} catch (TransformerConfigurationException e) {
				throw new IOException("transformer config error");
			} catch (TransformerException e) {
				System.out.println(Util.XMLToString(doc));
				System.out.println(e.getMessage());
				throw new IOException("transformer error");
			} catch (IOException e) {
				throw new IOException();
			} 

	}
	
	public void performAction(Action action) throws ActException {
		
		if ( connected == false ) {
			Connection();
			if ( connected == false ) {
				releaseConnection();
				throw new ActException(ActException.FAILURE,"no valid connection");
			}
		}
		
		Document doc = null;
		doc = Util.actionToXML(action, currentActionId);

		try {
			sendDoc(doc);
		} catch (IOException e) {
			releaseConnection();
			throw new ActException(ActException.FAILURE,"sending failed action",e);
		}
	}
	
	private void releaseConnection() {
		 	 
			 if ( socket != null ) {
				 
				 try {
					 socket.close();
				 }
				 catch(IOException e) {
					 e.printStackTrace();
				 }
				 
			 }
	
			 try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 
			 System.out.println("Déconnection");
			 
		 }
		 
}
